/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simple.utils.processing;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Tom
 */
public class Progress {

    private List<ProgressListener> listeners = new CopyOnWriteArrayList<>();
    private List<PercentListener> listeners2 = new CopyOnWriteArrayList<>();
    private Map<Integer, Long> durations = new ConcurrentHashMap<>();
    private AtomicLong progress = new AtomicLong(0);
    private volatile long total;
    private volatile int lastPercent = 0;
    private volatile long lastProcessed = 0;
    private final ReentrantLock lock = new ReentrantLock();
    private final Timer timer = new Timer();
    private final StringBuilder logBuilder = new StringBuilder();

    public Progress(long total) {
        this.total = total;
    }

    public Progress(long total, String initialLogMsg) {
        this.total = total;
        appendLogMsg(initialLogMsg);
    }

    public void addTotal(long inc) {
        lock.lock();
        try {
            total += inc;
        } finally {
            lock.unlock();
        }
    }

    public String getLog() {
        return logBuilder.toString();
    }

    public void appendLogMsg(String msg) {
        lock.lock();
        try {
            logBuilder.append(msg);
            logBuilder.append("\n");
        } finally {
            lock.unlock();
        }
    }

    public void done() {
        for (ProgressListener pl : listeners) {
            pl.onDone();
        }
        if (timer.isStarted()) {
            timer.stop();
        }
    }

    public void startTimer() {
        timer.start();
    }

    public void stopTimer() {
        timer.stop();
    }

    public long getDuration(TimeUnit timeunit) {
        return timer.getDuration(timeunit);
    }

    public void addPercentListener(PercentListener listener) {
        listeners2.add(listener);
    }

    public void removePercentListener(PercentListener listener) {
        listeners2.remove(listener);
    }

    public void addProgressListener(ProgressListener listener) {
        listeners.add(listener);
    }

    public void removeProgressListener(ProgressListener listener) {
        listeners.remove(listener);
    }

    /**
     * Add processed units count
     *
     * @param unitCount number of completed units
     */
    public void addCompleted(long unitCount) {
        lock.lock();
        try {
            long num = progress.addAndGet(unitCount);
            if (num != lastProcessed) {
                lastProcessed = num;
            }
            fireCompletedChanged(num);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns progress in %
     *
     * @return
     */
    public int getProgress() {
        lock.lock();
        try {
            float tot = (float) total;
            float prog = (float) progress.get();
            float fract = prog / tot;
            int per = (int) (fract * 100);
            return per;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns total units of work
     *
     * @return total units of work
     */
    public long getTotal() {
        return total;
    }

    /**
     * Returns completed units of work
     *
     * @return completed units
     */
    public long getCompleted() {
        lock.lock();
        try {
            return progress.get();
        } finally {
            lock.unlock();
        }
    }

    private void fireCompletedChanged(long num) {
        lock.lock();
        try {
            int percent = getProgress();
            boolean percentChanged = false;
            if (lastPercent != percent) {
                percentChanged = true;
                lastPercent = percent;
            }
            if (percentChanged) {
                if (timer.isStarted()) {
                    this.durations.put(percent, timer.getDuration(TimeUnit.MILLISECONDS));
                }
            }
            for (ProgressListener listener : listeners) {
                listener.processedChanged(num);
                if (percentChanged) {
                    listener.progressChanged(percent);
                }
            }
            if (percentChanged) {
                for (PercentListener pl : listeners2) {
                    pl.progressChanged(percent);
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public void logTime() {
        appendLogMsg("Running for " + timer.getDuration(TimeUnit.SECONDS) + " seconds");
    }
}
